﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Composition;
using Windows.Foundation;
using Windows.Storage;
using CC98.Security;
using CC98.ComponentModel;
using CC98.Composition;

namespace CC98.Network.Primitives
{
    [Shared]
    [InitializeExport(typeof(ICC98HttpClientPool), ActionScope.App)]
    public class DefaultCC98HttpClientPool : SingleInstanceObject, ICC98HttpClientPool, IInitializeHandler, ISuspendHandler
    {
        public DefaultCC98HttpClientPool()
        {
            // Load session
        }

        /// <summary>
        /// Get a CC98HttpClient for a specify user
        /// This method is thread-safe
        /// </summary>
        /// <param name="username">The username</param>
        /// <returns></returns>
        public CC98HttpClient GetClient(string username)
        {
            if (string.IsNullOrWhiteSpace(username))
                throw new ArgumentException("Invalid username");
            lock (this)
            {
                if (this._clientDict.ContainsKey(username))
                {
                    return this._clientDict[username];
                }
                else
                {
                    var client = new CC98HttpClient(new CC98HttpClientHandler(username));
                    this._clientDict[username] = client;
                    return client;
                }
            }
        }

        /// <summary>
        /// Get client for a specify session
        /// </summary>
        /// <param name="session">The session</param>
        /// <param name="isOverwrite">If true, the new client will overwrite the exists client</param>
        /// <returns></returns>
        public CC98HttpClient GetClient(Session session, bool isOverwrite)
        {
            if (session == null)
                throw new ArgumentNullException("session");
            lock (this)
            {
                if (this._clientDict.ContainsKey(session.Username) && !isOverwrite)
                    throw new InvalidOperationException("User " + session.Username + " has already exists and cannot be overwritten");
                var client = new CC98HttpClient(new CC98HttpClientHandler(session));
                this._clientDict[session.Username] = client;
                return client;
            }
        }

        public IAsyncOperation<CC98HttpClient> LoginClientAsync(CC98Certificate certificate)
        {
            if (certificate == null)
                throw new ArgumentNullException("certificate");
            return AsyncInfo.Run<CC98HttpClient>(new Func<CancellationToken, Task<CC98HttpClient>>((ct) =>
                {
                    var client = this.GetClient(certificate.Username);
                    if (!client.IsAuthenticated ?? false)
                        return this.InternalLoginClientAsync(client, certificate);
                    else
                        return Task.FromResult<CC98HttpClient>(client);
                }));
        }

        protected void LoadSessions()
        {
            // Load network session
            List<Session> sessions = new List<Session>();

            ApplicationDataContainer container = null;
            if (ApplicationData.Current.RoamingSettings.Containers.TryGetValue("session", out container))
            {
                foreach (var sessionContainer in container.Containers)
                {
                    var session = new Session(sessionContainer.Value);
                    sessions.Add(session);
                }
            }
            // Create user sessions
            foreach (var session in sessions)
            {
                this.GetClient(session, true);
            }
        }

        /// <summary>
        /// Save sessions to setting
        /// </summary>
        public void SaveSessions()
        {
            ApplicationDataContainer container = null;
            if (ApplicationData.Current.RoamingSettings.Containers.TryGetValue("session", out container))
            {
                container = ApplicationData.Current.RoamingSettings.CreateContainer("session", ApplicationDataCreateDisposition.Always);
            }

            foreach (var client in this.GetAllClients())
            {
                ApplicationDataContainer sessionContainer = null;
                if (!container.Containers.TryGetValue(client.Username, out sessionContainer))
                    sessionContainer = container.CreateContainer(client.Username, ApplicationDataCreateDisposition.Always);
                client.GetSession().Write(sessionContainer);
            }
        }

        #region Initialize & suspend


        public void Suspend(SuspendMode mode)
        {
            this.SaveSessions();
        }

        public string Name
        {
            get
            {
                return "Network Session";
            }
        }

        public void Initialize()
        {
            this.LoadSessions();
        }

        #endregion

        public IEnumerable<CC98HttpClient> GetAllClients()
        {
            lock (this)
            {
                var clients = new CC98HttpClient[this._clientDict.Count];
                this._clientDict.Values.CopyTo(clients, 0);
                return clients;
            }
        }

        protected async Task<CC98HttpClient> InternalLoginClientAsync(CC98HttpClient client, CC98Certificate certificate)
        {
            if (certificate == null)
                throw new ArgumentNullException("certificate");

            var r = await client.LoginAsync(certificate);

            if (r.Error != null)
                throw r.Error;

            return client;
        }

        #region private

        private readonly Dictionary<string, CC98HttpClient> _clientDict = new Dictionary<string, CC98HttpClient>();

        #endregion
    }
}
